package com.bob.like.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bob.like.constant.BlogConstant;
import com.bob.like.constant.ThumbConstant;
import com.bob.like.eunm.ThumbStatus;
import com.bob.like.mapper.ThumbMapper;
import com.bob.like.model.dto.DoThumbRequest;
import com.bob.like.model.entity.Blog;
import com.bob.like.model.entity.Thumb;
import com.bob.like.model.entity.User;
import com.bob.like.service.BlogService;
import com.bob.like.service.ThumbService;
import com.bob.like.service.UserService;
import com.bob.like.util.RedisKeyUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.servlet.http.HttpServletRequest;
import java.util.Currency;
import java.util.concurrent.TimeUnit;

@Service("thumbServiceDB")
@Slf4j
@RequiredArgsConstructor
public class ThumbServiceImpl extends ServiceImpl<ThumbMapper, Thumb> implements ThumbService {
  
    private final UserService userService;
  
    private final BlogService blogService;
  
    private final TransactionTemplate transactionTemplate;

    private final RedissonClient redissonClient;

    private final RedisTemplate<String, Object> redisTemplate;


    @Override  
    public Boolean doThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        if (doThumbRequest == null || doThumbRequest.getBlogId() == null) {  
            throw new RuntimeException("参数错误");  
        }  
        User loginUser = userService.getLoginUser(request);
        // 加锁
        RLock lock = redissonClient.getLock(loginUser.getId().toString().intern());
        boolean tryLock = false;
        try{
            tryLock = lock.tryLock(1,1,TimeUnit.SECONDS);
            if (!tryLock) {
                throw new RuntimeException("操作太频繁，请稍后再试");
            }
            // 编程式事务
            return transactionTemplate.execute(status -> {
                Long blogId = doThumbRequest.getBlogId();
                // 将查询从数据库移到缓存中
                boolean exists = this.hasThumb(blogId, loginUser.getId());
                if (exists) {
                    throw new RuntimeException("用户已点赞");
                }
                //当点赞记录不存在时，也有可能是因为 redis 过期了，所以需要去数据库查询
                //如果 redis 过期了，但是数据库没有数据，那么说明用户没有点赞，所以可以进行点赞操作
                //如果 redis 过期了，但是数据库有数据，那么说明用户已经点赞了，所以不能进行点赞操作
                long currentTimeMillis = System.currentTimeMillis();
                long createTimeMillis = getBlogCreateTime(blogId);
                if (currentTimeMillis - createTimeMillis > BlogConstant.BLOG_EXPIRE_TIME){
                    Thumb thumb = this.lambdaQuery()
                            .eq(Thumb::getUserId, loginUser.getId())
                            .eq(Thumb::getBlogId, blogId)
                            .one();
                    if(thumb != null){
                        throw new RuntimeException("用户已点赞");
                    }
                }

                boolean update = blogService.lambdaUpdate()
                        .eq(Blog::getId, blogId)
                        .setSql("thumbCount = thumbCount + 1")
                        .update();
                Thumb thumb = new Thumb();
                thumb.setUserId(loginUser.getId());
                thumb.setBlogId(blogId);
                boolean success =  update && this.save(thumb);
                //如果成功点赞了，则将用户id和博客id存入redis
                //todo 如果存入 redis 时，redis 宕机了怎么办？
                //todo redis 存入的数据太多了，如果设置过期时间，那么查不到的时候，因为不知道是过期了还是没有但赞，所以需要去数据库查询，并且大部分博客都是未点赞的，这这样不仅没有降低 mysql 读压力，还多了一次 redis 请求。
                //todo 如何解决：冷热数据分离，如果发布时间超过一个月的博客进行点赞，则查 mysql。反之查 redis ，简而言之就是缓存过期时间是一个月，
                //todo 但是鉴于 hash 结构不能设置过期时间，
                if(success){
                    //redisTemplate.opsForHash().put(ThumbConstant.USER_THUMB_KEY_PREFIX + loginUser.getId().toString(), blogId.toString(), blogId);
                    redisTemplate.opsForValue().set(RedisKeyUtil.getUserThumbKey(loginUser.getId(),blogId), thumb.getId(),ThumbConstant.THUMB_EXPIRE_TIME,TimeUnit.MILLISECONDS);
                }
                return success;
            });
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            // 检查锁是否被当前线程持有，避免误释放锁
            if (tryLock && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }

    }

    @Override
    public Boolean undoThumb(DoThumbRequest doThumbRequest, HttpServletRequest request) {
        if (doThumbRequest == null || doThumbRequest.getBlogId() == null) {
            throw new RuntimeException("参数错误");
        }
        User loginUser = userService.getLoginUser(request);
        // 编程式事务
        return transactionTemplate.execute(status -> {
            Long blogId = doThumbRequest.getBlogId();

            Object thumbId = redisTemplate.opsForValue().get(RedisKeyUtil.getUserThumbKey(loginUser.getId(),blogId));
            if (ObjectUtil.isNull(thumbId)) {
                //有两种情况：1.用户未点赞 2.用户点赞后，redis 过期了
                //所以首先要判断博客发表时间是否超过了一个月
                long currentTimeMillis = System.currentTimeMillis();
                long createTimeMillis = getBlogCreateTime(blogId);
                Thumb thumb = null;
                if (currentTimeMillis - createTimeMillis > BlogConstant.BLOG_EXPIRE_TIME) {
                    thumb = this.lambdaQuery()
                            .eq(Thumb::getUserId, loginUser.getId())
                            .eq(Thumb::getBlogId, blogId)
                            .one();
                }
                if(thumb != null){
                    thumbId = thumb.getId();
                }else{
                    throw new RuntimeException("用户未点赞");
                }
            }

            boolean update = blogService.lambdaUpdate()
                    .eq(Blog::getId, blogId)
                    .setSql("thumbCount = thumbCount - 1")
                    .update();

            boolean success = update && this.removeById(Long.valueOf(thumbId.toString()));
            if(success){
                redisTemplate.delete(RedisKeyUtil.getUserThumbKey(loginUser.getId(),blogId));
            }
            return success;
        });
    }



    @Override
    public Boolean hasThumb(Long blogId, Long userId) {
        //return redisTemplate.opsForHash().hasKey(ThumbConstant.USER_THUMB_KEY_PREFIX + userId, blogId.toString());
        return ObjectUtil.isNotNull(redisTemplate.opsForValue().get(RedisKeyUtil.getUserThumbKey(userId,blogId))) ;
    }

    public long getBlogCreateTime(long blogId) {
        Object blogCreateTime = redisTemplate.opsForValue().get(BlogConstant.BLOG_KEY_PREFIX + blogId);
        if(blogCreateTime == null){
            Blog blog = blogService.getById(blogId);
            blogCreateTime = blog.getCreateTime().getTime();
            redisTemplate.opsForValue().set(BlogConstant.BLOG_KEY_PREFIX + blogId, blogCreateTime, BlogConstant.BLOG_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        }
        return blogCreateTime instanceof Long ? (Long) blogCreateTime : Long.parseLong(blogCreateTime.toString());
    }

}
